﻿#if !UNITY_IOS || UNITY_EDITOR
#define USE_REFLECTION
#endif

using Devil;
using System.Collections.Generic;
using System.IO;
using System.Reflection;

#if UNITY_EDITOR
using UnityEditor;
#if UNITY_2021_1_OR_NEWER
using UnityEditor.SceneManagement;
#else
#endif
#endif
using UnityEngine;

namespace GameToolkit
{
    public static class GlobalUtil
    {
        static readonly object _lock = new object();
        static Stack<Transform> sTmpStack = new Stack<Transform>();
        public static Stack<Transform> CachedTransformStack { get { return sTmpStack; } }
        static long _idPtr;
#if UNITY_EDITOR

        public static string ActiveProjectFolder
        {
            get
            {
                string folder;
                var t = Selection.activeObject;
                if (t == null)
                {
                    folder = "Assets";
                }
                else
                {
                    folder = AssetDatabase.GetAssetPath(t);
                    if (File.Exists(folder))
                    {
                        var index = folder.LastIndexOf('/');
                        folder = folder.Substring(0, index);
                    }
                }
                return folder;
            }
        }

        public static string[] GetActiveProjectFolders()
        {
            var selection = Selection.objects;
            var folders = new List<string>();
            foreach (var obj in selection)
            {
                var path = AssetDatabase.GetAssetPath(obj);
                if (Directory.Exists(path))
                    folders.Add(path);
            }
            if (folders.Count == 0)
                folders.Add(ActiveProjectFolder);
            return folders.ToArray();
        }

        public static string GetPrefabPath(Object target, bool rootOnly = true)
        {
            GameObject gameObject;
            if (target is GameObject)
                gameObject = (GameObject)target;
            else if (target is Component)
                gameObject = ((Component)target).gameObject;
            else
                gameObject = null;
            if (gameObject == null)
                return null;
            var stage = PrefabStageUtility.GetCurrentPrefabStage();
            if (stage != null)
#if UNITY_2021_1_OR_NEWER
                return !rootOnly || stage.prefabContentsRoot == gameObject ? stage.assetPath : null;
#else
                return !rootOnly || stage.prefabContentsRoot == gameObject ? stage.prefabAssetPath : null;
#endif
            //var asset = stage != null && (stage.prefabContentsRoot == gameObject || gameObject.transform.IsChildOf(stage.prefabContentsRoot.transform)) ?
            //    stage.prefabAssetPath : 
            return PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(gameObject);
            //targetName = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(target);
        }

        public static bool IsPrefabStage(GameObject go)
        {
            if (go == null)
                return false;
            var stage = PrefabStageUtility.GetCurrentPrefabStage();
            return stage != null && stage.scene == go.scene;
        }

        public static bool IsPrefabStage()
        {
            var stage = PrefabStageUtility.GetCurrentPrefabStage();
            return stage != null;
        }

#else
        public static bool IsPrefabStage(GameObject go)
        {
            return false;
        }

        public static bool IsPrefabStage()
        {
            return false;
        }
#endif

        public static void MakeNoZeroUniqueArray(IList<int> lst)
        {
            for (int i = lst.Count - 1; i > 0; i--)
            {
                if (lst[i] == 0)
                {
                    lst.RemoveAt(i);
                    continue;
                }
                for (int j = i - 1; j >= 0; j--)
                {
                    if (lst[i] == lst[j])
                    {
                        lst.RemoveAt(i);
                        break;
                    }
                }
            }
        }

        public static void MakeRandomList<T>(IList<T> array, int start, int count)
        {
            MakeRandomList(array, start, count, count);
        }

        public static void MakeRandomList<T>(IList<T> array, int start, int count, int dataCount)
        {
            T tmp;
            for (int i = 1; i < count; i++)
            {
                var index = i - 1 + start;
                var r = Mathf.RoundToInt(ParallelUnity.GetRandomValue() * (dataCount - i - 1)) + i + start;
                tmp = array[r];
                array[r] = array[index];
                array[index] = tmp;
            }
        }

        public static bool IsTypeInheritedFrom(System.Type type, System.Type baseType, bool considerSelf = true)
        {
            System.Type tp = considerSelf ? type : type.BaseType;
            while (tp != null)
            {
                if (tp == baseType)
                    return true;
                tp = tp.BaseType;
            }
            return false;
        }

        public static bool IsValid(this AnimationCurve curve)
        {
            return curve != null && curve.length > 1;
        }

        public static float GetMinTime(this AnimationCurve curve)
        {
            return curve.keys[0].time;
        }

        public static float GetMaxTime(this AnimationCurve curve)
        {
            return curve.keys[curve.length - 1].time;
        }

        public static float ClampTime(this AnimationCurve curve, float time)
        {
            return Mathf.Clamp(time, GetMinTime(curve), GetMaxTime(curve));
        }

        public static float GetEndValue(this AnimationCurve curve)
        {
            return curve.keys[curve.length - 1].value;
        }

        public static float GetStartValue(this AnimationCurve curve)
        {
            return curve.keys[0].value;
        }

        public static float GetNormalizedValue(this AnimationCurve curve, float t)
        {
            float tmin = GetMinTime(curve);
            float tmax = GetMaxTime(curve);
            float lerp = Mathf.Lerp(tmin, tmax, t);
            float v = curve.Evaluate(lerp);
            tmin = GetStartValue(curve);
            tmax = GetEndValue(curve);
            float len = Mathf.Abs(tmax - tmin);
            if (tmin > tmax)
                tmin = tmax;
            return len == 0 ? v : (v - tmin) / len;
        }

        public static int GenUniqueID()
        {
            var v = System.DateTime.UtcNow.Ticks;
            lock (_lock)
            {
                return unchecked((int)((v << 5) - v + ++_idPtr));
            }
        }

        //public static int Hash(int a, int b)
        //{
        //    return (a << 5) - a + b;
        //}

        //public static int CalculateHashCode(params object[] args)
        //{
        //    int v = 0;
        //    for (int i = 0; i < args.Length; i++)
        //    {
        //        v = (v << 5) - v + (args[i] == null ? 0 : args[i].GetHashCode());
        //    }
        //    return v;
        //}

        public static bool IsValidInstance(this object obj)
        {
            if (obj == null)
                return false;
            else if (obj is IValidator val)
                return val.IsValid;
            else if (obj is UnityEngine.Object uobj)
                return uobj != null;
            else
                return true;
        }

        public static Mesh BakeBlendShape(Mesh mesh, float[] blendShapes, bool autoUpload = true)
        {
            var verts = mesh.vertices;
            var normals = mesh.normals;
            var deltaVerts = new Vector3[verts.Length];
            var deltaNor = new Vector3[normals.Length];
            var deltaTans = new Vector3[verts.Length];
            var copy = Object.Instantiate(mesh);
#if UNITY_EDITOR
            copy.hideFlags |= HideFlags.DontSave;
#endif
            var shapes = mesh.blendShapeCount;
            for (int i = 0; i < shapes; i++)
            {
                var frames = mesh.GetBlendShapeFrameCount(i);
                if (frames == 0)
                    continue;
                var weight = mesh.GetBlendShapeFrameWeight(i, 0);
                if (weight > 0)
                    weight = 1f / weight;
                weight = Mathf.Clamp01(weight * blendShapes[i]);
                mesh.GetBlendShapeFrameVertices(i, 0, deltaVerts, deltaNor, deltaTans);
                for (int k = 0; k < verts.Length; k++)
                {
                    verts[k] += deltaVerts[k] * weight;
                    if (i == shapes - 1)
                        normals[k] = (normals[k] + deltaNor[k] * weight).normalized;
                    else
                        normals[k] += deltaNor[k] * weight;
                }
            }
            copy.vertices = verts;
            copy.normals = normals;
            copy.RecalculateTangents();
            if (autoUpload)
                copy.UploadMeshData(true);
            return copy;
        }

        public static void QuitApplication()
        {
#if UNITY_EDITOR
            EditorApplication.isPlaying = false;
#else
            Application.Quit();
#endif
        }

        public static GameObject Instantiate(Object prefab, Vector3 pos, Quaternion rot, Transform parent)
        {

#if UNITY_EDITOR
            if (!Application.isPlaying)
            {
                var go = PrefabUtility.InstantiatePrefab(prefab, parent) as GameObject;
                go.hideFlags |= HideFlags.DontSaveInEditor | HideFlags.DontSaveInBuild;
                var trans = go.transform;
                trans.position = pos;
                trans.rotation = rot;
                return go;
            }
            else
#endif
                return Object.Instantiate(prefab, pos, rot, parent) as GameObject;
        }

        public static void Destroy(Object go, bool deleteAsset = false)
        {
            ParallelUnity.DestroyAutomatic(go, deleteAsset);
        }

        public static object[] GetMethodsWithAttribute<T>(System.Type type, bool inherit = true) where T : System.Attribute
        {
#if USE_REFLECTION
            List<MethodInfo> methods = new List<MethodInfo>();
            MethodInfo[] mtds = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            for (int i = 0; i < mtds.Length; i++)
            {
                object[] attr = mtds[i].GetCustomAttributes(typeof(T), inherit);
                int len = attr == null ? 0 : attr.Length;
                if (len > 0)
                {
                    methods.Add(mtds[i]);
                }
            }
            return methods.ToArray();
#else
            Debug.LogWarning("Reflection is not supported but \"Ref.GetMethodsWithAttribute<T>(Type)\" require it!");
            return null;
#endif
        }

        public static object[] GetMethodsWithParams(System.Type type, System.Type retType, System.Type[] paramTypes)
        {
#if USE_REFLECTION
            List<MethodInfo> methods = new List<MethodInfo>();
            MethodInfo[] mtds = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            for (int i = 0; i < mtds.Length; i++)
            {
                if (MatchMethodRetAndParams(mtds[i], retType, paramTypes))
                {
                    methods.Add(mtds[i]);
                }
            }
            return methods.ToArray();
#else
            return null;
#endif
        }

        public static bool MatchMethodRetAndParams(object method, System.Type retType, System.Type[] paramTypes)
        {
#if USE_REFLECTION
            MethodInfo mtd = method as MethodInfo;
            if (mtd == null)
                return false;
            if (mtd.ReturnType != retType)
                return false;
            ParameterInfo[] mparam = mtd.GetParameters();
            int mlen = mparam == null ? 0 : mparam.Length;
            int slen = paramTypes == null ? 0 : paramTypes.Length;
            if (mlen != slen)
                return false;
            for (int i = 0; i < mlen; i++)
            {
                if (mparam[i].ParameterType != paramTypes[i])
                {
                    return false;
                }
            }
            return true;
#else
            Debug.LogWarning("Reflection is not supported but \"Ref.MatchMethodWithDelegate<T>(object, T)\" require it!");
            return false;
#endif
        }


#if UNITY_EDITOR
        public static void GetTypesInAssembly(Assembly assembly, ICollection<System.Type> collection, FilterDelegate<System.Type> filter)
        {
            if (assembly == null)
                return;
            try
            {
                var types = assembly.GetTypes();
                for (int i = 0; i < types.Length; i++)
                {
                    if (filter == null || filter(types[i]))
                        collection.Add(types[i]);
                }
            }
            catch (System.Exception e)
            {
                Debug.LogErrorFormat("Faild to load assembly: {0}\n{1}", assembly, e);
            }
        }

        public static void GetAssemblyTypes(ICollection<System.Type> types, FilterDelegate<System.Type> filter = null, FilterDelegate<Assembly> assemblyFilter = null)
        {
            Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assembly in assemblies)
            {
                if (assemblyFilter == null || assemblyFilter(assembly))
                {
                    GetTypesInAssembly(assembly, types, filter);
                }
                //foreach (Type foundType in GetTypesInAssembly(assembly, predicate))
                //    foundTypes.Add(foundType);
            }
            //return foundTypes;
        }
#endif
    }
}